package com.demo.upload.service.impl;


import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.demo.upload.dto.FileChunkDto;
import com.demo.upload.entity.FileChunk;
import com.demo.upload.entity.FileStorage;
import com.demo.upload.mapper.FileStorageMapper;
import com.demo.upload.service.FileChunkService;
import com.demo.upload.service.FileStorageService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * 文件存储表服务实现类
 */
@Service("fileStorageService")
@Slf4j
public class FileStorageServiceImpl extends ServiceImpl<FileStorageMapper, FileStorage> implements FileStorageService {
    /**
     * 默认分块大小
     */
    @Value("${file.chunk-size}")
    public Long defaultChunkSize;
    @Resource
    FileChunkService fileChunkService;
    /**
     * 上传地址
     */
    @Value("${file.path}")
    private String baseFileSavePath;

    @Override
    public Boolean uploadFile(FileChunkDto dto) {
        // 简单校验
        if (dto.getFile() == null) {
            throw new RuntimeException("文件不能为空");
        }
        String fullFileName = baseFileSavePath + File.separator + dto.getFilename();
        Boolean uploadFlag;
        // 单文件上传
        if (dto.getTotalChunks() == 1) {
            uploadFlag = this.uploadSingleFile(fullFileName, dto);
        } else {
            // 分片上传
            uploadFlag = this.uploadSharding(fullFileName, dto);
        }
        // 如果本次上传成功则存储上传进度到数据到表中
        if (uploadFlag) {
            this.saveFile(dto, fullFileName);
        }
        return uploadFlag;
    }

    /**
     * 分片上传方法
     * 使用 RandomAccessFile方法上传(省去文件合并的过程)
     */
    private Boolean uploadSharding(String fullFileName, FileChunkDto dto) {
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(fullFileName, "rw")) {
            // 分片大小必须和前端匹配，否则上传会导致文件损坏
            long chunkSize = dto.getChunkSize() == 0L ? defaultChunkSize : dto.getChunkSize().longValue();
            // 偏移量, 获取当前需要写入文件的位置，分片大小 * 已经存的块数
            long offset = chunkSize * (dto.getChunkNumber() - 1);
            // 定位到该分片的偏移量
            randomAccessFile.seek(offset);
            // 写入
            randomAccessFile.write(dto.getFile().getBytes());
        } catch (IOException e) {
            log.error("文件上传失败：" + e);
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    @SneakyThrows
    private Boolean uploadSingleFile(String fullFileName, FileChunkDto dto) {
        File localPath = new File(fullFileName);
        dto.getFile().transferTo(localPath);
        return Boolean.TRUE;
    }

    private void saveFile(FileChunkDto dto, String fileName) {

        FileChunk chunk = BeanUtil.copyProperties(dto, FileChunk.class);
        chunk.setFileName(dto.getFilename());
        chunk.setTotalChunk(dto.getTotalChunks());
        fileChunkService.save(chunk);
        // 全部上传完成记录到数据库中
        if (dto.getChunkNumber().equals(dto.getTotalChunks())) {
            String name = dto.getFilename();
            MultipartFile file = dto.getFile();

            FileStorage fileStorage = new FileStorage();
            fileStorage.setRealName(file.getOriginalFilename());
            fileStorage.setFileName(dto.getRelativePath());
            fileStorage.setSuffix(cn.hutool.core.io.FileUtil.getSuffix(name));
            fileStorage.setFileType(file.getContentType());
            fileStorage.setSize(dto.getTotalSize());
            fileStorage.setIdentifier(dto.getIdentifier());
            fileStorage.setFilePath(fileName);
            this.save(fileStorage);
        }
    }
}

